Skip to content

fix(cuda.bindings): Make param_packer.feed() free-threading-safe via import-time init - #2417

Open
clin1234 wants to merge 18 commits into
NVIDIA:mainfrom
clin1234:threading-fixes
Open

fix(cuda.bindings): Make param_packer.feed() free-threading-safe via import-time init#2417
clin1234 wants to merge 18 commits into
NVIDIA:mainfrom
clin1234:threading-fixes

Conversation

@clin1234

Copy link
Copy Markdown

Description

Restructure param_packer.h so the ctypes type pointers and the feeder table are built once in a new init_param_packer(), called at module import (from utils.pxi) while single-threaded. feed() becomes a read-only lookup on a never-mutated std::map, so concurrent kernel launches from multiple host threads no longer race the map or the ctypes lazy-init -- races that were live in the cp315t (Py_MOD_GIL_NOT_USED) wheels. init_param_packer is declared 'except +' so a failed 'import ctypes' surfaces as a Python exception at import instead of std::terminate across the Cython boundary, and feed() is now non-throwing. The ctypes module strong ref is kept deliberately (it keeps the borrowed type pointers valid).

Harden resource_handles.cpp: make mr_dealloc_cb std::atomic with acquire/release ordering, and document the verified single-init of initialize_deferred_cleanup() and the mutation-only lazy path for GraphBox::slot_table (both already safe; no behavior change).

closes #2416

Restructure param_packer.h so the ctypes type pointers and the feeder
table are built once in a new init_param_packer(), called at module import
(from utils.pxi) while single-threaded. feed() becomes a read-only lookup on
a never-mutated std::map, so concurrent kernel launches from multiple host
threads no longer race the map or the ctypes lazy-init -- races that were
live in the cp315t (Py_MOD_GIL_NOT_USED) wheels. init_param_packer is
declared 'except +' so a failed 'import ctypes' surfaces as a Python
exception at import instead of std::terminate across the Cython boundary,
and feed() is now non-throwing. The ctypes module strong ref is kept
deliberately (it keeps the borrowed type pointers valid).

Harden resource_handles.cpp: make mr_dealloc_cb std::atomic with
acquire/release ordering, and document the verified single-init of
initialize_deferred_cleanup() and the mutation-only lazy path for
GraphBox::slot_table (both already safe; no behavior change).
@copy-pr-bot

copy-pr-bot Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@github-actions github-actions Bot added cuda.bindings Everything related to the cuda.bindings module cuda.core Everything related to the cuda.core module labels Jul 23, 2026
@clin1234
clin1234 marked this pull request as ready for review July 24, 2026 16:01
@clin1234 clin1234 changed the title Make param_packer.feed() free-threading-safe via import-time init fix(cuda.bindings): Make param_packer.feed() free-threading-safe via import-time init Jul 24, 2026

@seberg seberg left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, for finding/fixing these issues. Moving the ctypes import/populating to import time seems OK to me (at worst 5% seems like additional import time).
I hate to ask it, but touching this, it would be nice to clean up the param packer initialization a bit more.

That said, the other two changes seem a bit strange to me and maybe because of merge conflicts? (I.e. the graphbox clearly won't compile)

if (mr_dealloc_cb) {
mr_dealloc_cb(mr, b->resource, size, b->h_stream);
if (MRDeallocCallback cb =
mr_dealloc_cb.load(std::memory_order_acquire)) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure I am following, this seems like unnecessary hardening?

If this was a general pattern, I might not mind, but I think we have many of these globals that we assume are initialized once at module init time and if that is the only time it is set, it is fine?

Comment thread cuda_core/cuda/core/_cpp/resource_handles.cpp Outdated
void init_param_packer() except +
# Hot path: read-only lookup, does not throw -> intentionally left as an
# implicit-noexcept extern (no `except +` needed).
int feed(void* ptr, object o, object ct)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like a regression?

You are right, this function can get away without the except + (if we can, I wouldn't mind encoding that on the C++ side for clarity, though).

But, it clearly needs to have the except -1! (you can drop the ? not that it matters)
I don't know if your analysis was based on some old branch where Python errors were not anticipated?!

# Populates ctypes pointers + the feeder table once, at import. `except +`
# translates a C++ throw (e.g. failed `import ctypes`) into a Python
# exception during module import, preserving any already-set Python error.
void init_param_packer() except +

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
void init_param_packer() except +
void init_param_packer() except +*

(For me, I would personally like if it actually propagated python errors right away.)

{
m_feeders[{target_t,source_t}] = [](void* ptr, PyObject* value) -> int
{
*((long long*)ptr) = (long long)PyLong_AsLongLong(value);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is missing the error check :/!

// load-bearing: the ctypes_c_* pointers below are *borrowed* references into
// the ctypes module dict, and stay valid only while this strong ref keeps the
// module (and therefore its type objects) alive. Do not Py_DECREF it.
static PyObject* ctypes_module = nullptr;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we re-wire everything here, can we just remove this global and make all type objects strong references instead? That seems much clearer.

(Unfortunately, I guess we are not vendoring pythoncapi-compat, so we don't have PyDict_GetItemStringRef, here it's fine, so let's not worry, although I could see that we'll just need that in the future...)

Comment thread cuda_bindings/cuda/bindings/_lib/param_packer.h
ArsalanShakil and others added 15 commits July 30, 2026 16:39
…NVIDIA#2238)

Writing into a host-accessible array obtained via np.from_dlpack requires
NumPy 2.2.5+, because earlier versions return a read-only array
(numpy GH #28632). Several tests and examples guarded these writes at
NumPy 2.1.0, so on NumPy 2.1.0-2.2.4 they would error instead of skip.

Audit all NumPy version guards in cuda_core and bump the write-into-DLPack
sites to 2.2.5 with a consistent reason matching the existing correct guard
in test_launcher.py. Guards that only read DLPack arrays (e.g.
strided_memory_view_constructors.py) or skip for an unrelated NumPy fix
(test_utils.py, numpy#26501) are left at 2.1.

Also fix examples/memory_ops.py, which used a plain string comparison
(np.__version__ < "2.1.0") that is unreliable for multi-digit versions;
replace it with np.lib.NumpyVersion to match the other examples, and
standardize test_graph_builder.py off a hand-rolled tuple skipif onto
requires_module. Update affected PEP 723 dependency pins to numpy>=2.2.5.

Signed-off-by: Arsalan Shakil <shakil.arsalan@yahoo.com>
…iptors (NVIDIA#2376)

* Pathfinder: add support on cutensornet for library loading and header descriptors

* Pathfinder: add support on custatevec for library loading and header descriptors

* Pathfinder: add support on cudensitymat for library loading and header descriptors

* Pathfinder: add support on custabilizer for library loading and header descriptors

* Pathfinder: add support on cupauliprop for library loading and header descriptors

* switch to cuquantum metapackage to simplify dependency listing

---------

Co-authored-by: Ralf W. Grosse-Kunstleve <rwgkio@gmail.com>
…aders (NVIDIA#2239)

* fix(pathfinder): remove dead/misleading error handling in platform loaders

Two dead error-handling paths in the dynamic-lib platform loaders:

- load_dl_linux.load_with_system_search guarded abs_path with
  'if abs_path is None: raise RuntimeError("No expected symbol ...")'.
  abs_path_for_dynamic_library never returns None (it returns a resolved
  path or raises OSError), so the branch is unreachable and its message is
  factually wrong (it concerns dlinfo path resolution, not symbols). Drop
  the dead branch and let the descriptive OSError surface, matching the
  deterministic-loader policy of not masking discovery/load failures.

- load_dl_windows.add_dll_directory had 'if not result: pass', a no-op; the
  PATH update below already runs unconditionally. Remove the dead branch and
  clarify the comment on why PATH is updated regardless.

No behavior change: both removed branches were unreachable or no-ops.

Signed-off-by: Aryan <aryansputta@gmail.com>

* fix(pathfinder): simplify loader review comments

---------

Signed-off-by: Aryan <aryansputta@gmail.com>
Co-authored-by: Ralf W. Grosse-Kunstleve <rwgkio@gmail.com>
…VIDIA#2196)

* fix(pathfinder): make find_nvidia_binary_utility deterministic, never search CWD

find_nvidia_binary_utility assembled a bounded list of trusted directories
(NVIDIA wheel bin/, CONDA_PREFIX, CUDA_HOME/CUDA_PATH) and then delegated to
shutil.which(name, path=trusted_dirs). On Windows shutil.which prepends the
process current working directory to the search even when an explicit path=
is supplied, so a binary located in an arbitrary (possibly attacker-writable)
CWD could be returned in preference to the trusted CUDA / Conda / wheel
binary. That violates the pathfinder contract of a deterministic lookup over
a documented, bounded set of trusted roots.

Replace the shutil.which delegation with an explicit resolver that searches
only the trusted directories, in order, returning the first executable match.
The current working directory and ambient PATH are never consulted. POSIX
execute-bit (X_OK) and Windows extension semantics are preserved, so behavior
is unchanged except for removing the CWD/PATH leakage. Names resolved in the
existing trusted dirs return exactly as before.

Rewrites the search-path tests to assert the deterministic probe order and
adds TestResolveInTrustedDirs covering CWD isolation, first-match-wins,
empty/duplicate dir skipping, and POSIX non-executable rejection.

Fixes NVIDIA#2119

* feat(pathfinder): add CTK-root canary fallback to find_nvidia_binary_utility

After the deterministic search over the explicit trusted directories (NVIDIA
wheel bin/, CONDA_PREFIX, CUDA_HOME/CUDA_PATH) misses, fall back to a CTK-root
canary probe: resolve cudart through the OS dynamic loader, which honors
LD_LIBRARY_PATH on Linux and the native DLL search on Windows, derive the CUDA
Toolkit root from its absolute path, and search that root's bin layout.

This addresses the concern raised on NVIDIA#2196: users who follow the CUDA Linux
installation guide set LD_LIBRARY_PATH for libraries and PATH for executables.
The bounded finder alone would stop finding the utility for them because PATH
is intentionally never consulted. The canary fallback recovers that case
through LD_LIBRARY_PATH instead of PATH. LD_LIBRARY_PATH is still an attack
vector, but a significantly harder one to exploit than PATH, and the ambient
PATH and process CWD remain unused.

The canary runs only after the explicit trusted dirs miss, so the common
wheel/conda/CUDA_HOME cases never spawn the resolver subprocess. The
canary -> CTK-root resolution is factored into a shared
resolve_ctk_root_via_canary helper reused by the dynamic-library CTK-root
canary flow.

Adds tests for the fallback (found, ordering, Windows bin layout, not
consulted when found earlier, cached) and for resolve_ctk_root_via_canary.
Adds 1.6.0 release notes for the minor version bump.

* fix(pathfinder): satisfy mypy no-any-return in canary helpers

pre-commit.ci mypy flagged returning Any from resolve_ctk_root_via_canary and
_resolve_ctk_root_via_canary (both declared -> str | None), because
derive_ctk_root resolves to Any under the pathfinder mypy config. Bind the
result to an annotated local before returning, matching the pattern used
elsewhere in the package.

* fix(pathfinder): address binary finder review

* docs(pathfinder): prepare 1.6.0 release notes

* refactor(pathfinder): apply review feedback on binary finder

- Rename _is_executable_file to _is_executable_candidate; the helper marks
  a return candidate rather than proving OS executability. Drop its docstring
  now that the name is self-explanatory, and update the test patch target.
- Drop the 'ambient PATH is never consulted' sentence from
  resolve_ctk_root_via_canary; it is not universally true.

Signed-off-by: Aryan <aryansputta@gmail.com>

---------

Signed-off-by: Aryan <aryansputta@gmail.com>
Co-authored-by: Ralf W. Grosse-Kunstleve <rgrossekunst@nvidia.com>
…VIDIA#2396)

Pixi <0.71.0 re-ran the editable source build on every `pixi run`,
recompiling all ~29 Cython extensions in cuda_bindings (~27s) even when
nothing changed, because setuptools' editable_wheel uses fresh temp
build dirs so build_ext always sees the .so as older than its source.

Pixi 0.71.0 fixed this upstream with a content-addressed source-build
cache (prefix-dev/pixi#6285) plus separate Cython input tracking
(prefix-dev/pixi#6123). Locally verified: under 0.66 the 2nd no-op run
rebuilds 29 extensions with a fresh /tmp/*.build-temp; under 0.73 it
rebuilds nothing and returns instantly.

The fix also bumps the pixi.lock format from v6 to v7 (unreadable by
pixi 0.66), so this requires all contributors to move to pixi >=0.71.
Regenerated all six committed lock files to v7 accordingly.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…A#2357)

* test(cuda.core): define graph attachment ownership requirements

Preserve the validated lifetime and failure scenarios as executable specifications for the metadata redesign, alongside independent cleanup clarifications.

* refactor(cuda.core): define graph hierarchy storage

Establish canonical per-graph boxes, stable hierarchy ownership, and Level 1 graph identity before rebuilding attachment operations.

* refactor(cuda.core): canonicalize graph hierarchy handles

Give root and child graph handles stable per-graph boxes while sharing one hierarchy control block and registry identity.

* refactor(cuda.core): retain immutable node attachments

Replace the mutable graph-wide slot table with complete per-node user objects and intrusive deferred cleanup.

* refactor(cuda.core): import cloned graph attachments

Copy and rekey source attachment metadata into embedded graph hierarchies before publishing their canonical boxes.

* refactor(cuda.core): invalidate destroyed child graph state

Unregister and tombstone child graph boxes after CUDA destroys their owner while preserving stable storage for existing handles.

* fix(cuda.core): retain unidentified captured callbacks

Allow graph-level anonymous attachments so a captured host callback stays safe when CUDA commits it but its node cannot be recovered.

* test(cuda.core): cover graph hierarchy invalidation

Verify anonymous capture retention and proactive invalidation of child and conditional graph views.

* docs(cuda.core): explain graph attachment ownership

Orient contributors to versioned node ownership, graph hierarchy metadata, clone propagation, invalidation, and deferred cleanup.

* fix(cuda.core): harden graph attachment lifecycle

Make node identity atomic, preserve tombstoned graph identity, retry deferred cleanup safely, and cover final-reference launch ownership.

* docs(cuda.core): add 1.2.0 graph lifetime note

Document corrected graph attachment lifetime and callback-safe resource release.

* refactor(cuda.core): prepare attachments before graph mutation

Retain attachment owners and preallocate metadata before CUDA graph mutations so failures cannot leave nodes with unretained resources.

* fix(cuda.core): make attachment rollback portable

Carry rollback through the prepared attachment deleter so consumer extension modules do not depend on an out-of-line C++ symbol.

* docs(cuda.core): clarify graph handle semantics

Preserve the general no-invalidation guarantee while documenting borrowed child graphs as a narrow driver-owned exception, and tighten implementation comments for reviewers.

* docs(cuda.core): clarify graph attachment ownership

Reframe the design around parameter versions and driver-managed lifetimes, and keep mutation ordering guidance beside the implementation.

* docs(cuda.core): add graph ownership diagrams

Replace the ASCII sketch with Mermaid and illustrate the GraphHandle alias and control-block relationships for reviewers.

* docs(cuda.core): warn about graph attachment cycles

Document that driver-held Python references are opaque to cyclic GC and identify affected graph APIs.
* New API for nvrtc

* Apply suggestion from @mdboom
…p helper lifetime (NVIDIA#2399)

* Harden program-cache perms, binary-path resolution, and coredump helper lifetime

- cuda.core: create the on-disk program cache tree owner-only (0o700) and
  re-assert restrictive perms on POSIX, so cached device code cannot be read
  or planted by other local users regardless of the inherited umask.
- cuda.pathfinder: absolutize the resolved binary-utility path to honor the
  documented absolute-path contract; surface AddDllDirectory failures on
  Windows with GetLastError instead of silently swallowing them.
- cuda.bindings: retain the caller's bytes in _HelperCUcoredumpSettings so the
  borrowed pointer cannot outlive its backing buffer, and free the getter's
  1 KiB buffer in __dealloc__; clarify get_buffer_pointer's lifetime contract.

Adds regression tests for the cache permissions, path absolutization, and
coredump helper lifetime.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Simplify code comments on the hardening fixes

Shorten the explanatory comments to a line or two each and drop the internal
issue-number references (which don't resolve in this repo). No code changes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Remove agent_authored markers from the new tests

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Remove weak coredump buffer-lifetime test

It didn't actually guard the two lifetime fixes: the driver copies the path
during the set call (so the NVIDIA#379 latent UAF can't trigger), and a missing free
leaks silently (so NVIDIA#381 wouldn't fail the test either).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Silence bandit S103 on the intentionally world-writable test dir

The test pre-creates a 0o777 cache dir to verify the loader tightens it to
0o700; the permissive mask is the point of the test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test: expect absolutized binary path on Windows

The abspath change makes find_nvidia_binary_utility return a drive-qualified
path on Windows (C:\... ), so the three search-order tests must compare against
os.path.abspath(expected). No-op on Linux; fixes the Windows CI failures.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Raise on out-of-range c_int/c_byte kernel arguments instead of truncating

The c_int/c_byte param feeders silently narrowed an out-of-range Python int
(e.g. 2**32+5 -> 5). Range-check in the feeder and raise OverflowError; declare
feed() as 'except? -1' so Cython propagates the exception instead of swallowing
it. Addresses Glasswing V9.1 (leofang chose the raise option over matching
ctypes' silent wrap).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* param_packer: use PyLong_AsInt for c_int range check on Python 3.13+

Per Leo's review: on 3.13+ PyLong_AsInt converts and range-checks in one call
(raising OverflowError itself), so gate the manual INT_MIN/INT_MAX check to
pre-3.13 only. c_byte keeps the manual check (no 8-bit CPython equivalent).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* param_packer: unify c_int/c_byte range check via PyLong_AsLongAndOverflow

Per Leo's follow-up: use a single code path on all supported Pythons instead of
version-gating PyLong_AsInt. PyLong_AsLongAndOverflow flags out-of-long values
via its overflow out-param (no exception set), then we bounds-check the 32-bit
(c_int) / 8-bit (c_byte) target range and raise OverflowError. Drops the
PY_VERSION_HEX gate.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Split kernel-arg overflow change (NVIDIA#363) into its own PR (NVIDIA#2402)

The out-of-range c_int/c_byte -> OverflowError change is a distinct behavior
change; moved to a standalone PR so it can be reviewed separately from this
hardening batch. No functional change to the remaining hardening work.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Address review: scope cache perms to tmp, revert cybind docstring

Per @leofang's review on PR NVIDIA#2399:
- _file_stream.py: only the tmp/ staging dir is created owner-only (0o700).
  root/ and entries/ now inherit the umask / any pre-existing permissions so a
  deliberately shared kernel cache (e.g. group-writable on a compute cluster)
  keeps working. Dropped the post-mkdir chmod that would have re-tightened an
  existing shared directory. 0o700 in mkdir mode needs no chmod: umask only
  clears bits.
- _internal/utils.pyx: revert the get_buffer_pointer docstring change; that is
  cybind code and is out of scope for this PR.
- Tests updated to match: assert only tmp is 0o700, and that a pre-existing
  0o777 shared root is used as-is.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Document accepted security trade-off for shared program caches

Record the residual risk from narrowing the cache-permission hardening
(glasswing NVIDIA#359/NVIDIA#375) per PR NVIDIA#2399 review: committed entry files stay 0o600
(contents owner-only via mkstemp+os.replace), tmp/ is owner-only to protect
in-flight writes, but root/entries inherit the umask so shared caches keep
working. In a group-writable shared cache a group member could replace a
cached kernel; tamper-proofing that path needs load-time integrity checks and
is out of scope here.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Simplify cache permission comment; drop internal tool name

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Strip internal issue numbers from cache permission test docstring

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* pathfinder: use os.add_dll_directory() instead of raw kernel32 call

Per @leofang's review question: replace the direct ctypes
kernel32.AddDllDirectory() call with the stdlib os.add_dll_directory()
wrapper. Both call the same Win32 API, but the stdlib version drops the
hand-maintained argtypes/restype binding and reports failure as OSError
instead of a NULL cookie + GetLastError() check. Behavior is unchanged:
the returned handle is intentionally discarded (it has no finalizer, so
the directory stays on the search path for the process lifetime), and the
PATH mutation + failure warning are preserved.

Also strip an internal issue number from a test docstring.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…VIDIA#2242)

* toolshed: handle anonymous struct renames in check_cython_abi.py

The name of an anonymous struct is an implementation detail, so it is
acceptable for the name to change between builds (e.g. from the old
anon_structXX / anon_unionXX scheme to the newer MODULE__anon_podXX
scheme that Cython now emits).  This change makes the checker verify
that anonymous structs have the same *structure*, while tolerating name
changes, so spurious "Missing" / "Added" errors are no longer reported
for pure renames.

Three improvements:

1. `_format_base_type_name`: unwrap `CConstOrVolatileTypeNode` instead
   of falling through to its class name.  Const/volatile qualifiers do
   not affect ABI layout, and the old behaviour stored the Python class
   name literally ("CConstOrVolatileTypeNode*") in generated .abi.json
   files, which caused field-type mismatches when comparing builds where
   the qualifier was expressed differently.

2. `_build_anon_rename_map`: new iterative bottom-up pass that builds a
   mapping from old-style anon names (expected) to new-style anon_pod
   names (found) by matching field content.  Leaf structs (no anon-type
   fields) are matched first; each round normalises the remaining
   candidates using already-known renames, allowing parent structs that
   embed renamed children to match in subsequent rounds.

3. `_normalize_type`: uses `re.sub` with `\b` word-boundary anchors
   instead of plain `str.replace`, so a shorter name like "anon_struct1"
   cannot corrupt a longer one like "anon_struct12" if iteration order
   happens to process the shorter key first.

`check_structs` is updated to look up each expected struct through the
rename map, normalise expected field types before comparing, and skip
the "Added" report for new-style names that are confirmed renames.

Note: baselines generated with the old tool (containing
"CConstOrVolatileTypeNode*" type strings) must be regenerated with the
fixed tool before the rename-matching logic can work correctly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Ignore private parts of the ABI

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(cuda.core): isolate pending-call saturation cleanup

Run the process-global queue probe in a subprocess and explicitly drain its synthetic callbacks so parallel free-threaded tests cannot contaminate one another.

* test(cuda.core): keep subprocess probe self-contained

Pass the finalization timeout into the child so process isolation does not depend on importing test helpers from the temporary working directory.
…#2339)

* add an api to return module in ObjectCode to support calling legacy driver APIs

* cuda.core: clarify ObjectCode.handle vs get_module() docstrings

Document that .handle returns the native context-independent CUlibrary
for cuda.core/newer APIs, while get_module() is a legacy-interop bridge
returning a context-dependent CUmodule via cuLibraryGetModule.
…IDIA#2402)

* Raise OverflowError on out-of-range c_int/c_byte kernel arguments

An out-of-range Python int passed as a c_int/c_byte kernel argument was
silently narrowed (e.g. 2**32+5 -> 5). The feeders now range-check via
PyLong_AsLongAndOverflow + an explicit 32-bit/8-bit bounds check and raise
OverflowError; feed() is declared 'except? -1' so Cython propagates the
exception instead of swallowing it. Adds a GPU regression test.

Addresses Glasswing finding V9.1; behavior reviewed and shaped by @leofang
(single code path via PyLong_AsLongAndOverflow).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* param_packer: clarify why c_int/c_byte bound to int range, not long

A reviewer asked why the manual check uses INT_MIN/INT_MAX rather than the
function's long-based overflow flag. Add a comment: the target slot is 32-bit
(8-bit for c_byte), and long is 64-bit on LP64, so overflow alone misses
2**31..2**63 -- the explicit int-range check catches those. Comment-only.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* param_packer: use PyLong_AsInt for c_int range check (backport for <3.13)

Address review feedback on NVIDIA#2402: replace the hand-rolled c_int bounds
check with CPython's PyLong_AsInt, which does exactly the INT_MIN/INT_MAX
range check we want and raises OverflowError itself. PyLong_AsInt only
became public in Python 3.13, so add a file-local, version-gated backport
(a copy of the CPython implementation, made `static` because this header
is compiled into every extension module that includes it).

The c_byte feeder has no CPython equivalent, so it keeps the explicit
PyLong_AsLongAndOverflow bounds check; its comment is now self-contained
rather than referencing the (now-changed) c_int feeder.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions github-actions Bot added CI/CD CI/CD infrastructure cuda.pathfinder Everything related to the cuda.pathfinder module labels Jul 30, 2026
@clin1234

Copy link
Copy Markdown
Author

pre-commit.ci autofix

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CI/CD CI/CD infrastructure cuda.bindings Everything related to the cuda.bindings module cuda.core Everything related to the cuda.core module cuda.pathfinder Everything related to the cuda.pathfinder module

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Umbrella issues with free-threading and Cython bindings

9 participants